from functools import wraps
from uuid import uuid4
from contextlib import contextmanager
from collections import defaultdict

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base, declared_attr, DeclarativeMeta
from sqlalchemy import Column, Integer, String
from re import sub
from sqlalchemy.schema import Table, ForeignKey, UniqueConstraint, MetaData
from sqlalchemy.orm import relationship, backref
from sqlalchemy.types import Enum, DateTime, Boolean
import datetime
from sqlalchemy.sql.expression import exists, bindparam
from sqlalchemy.orm.query import Query
from sqlalchemy.orm.util import aliased, class_mapper
from sqlalchemy.orm.exc import NoResultFound, UnmappedClassError
from sqlalchemy.orm.session import object_session
from sqlalchemy.orm.collections import mapped_collection
from sqlalchemy.exc import IntegrityError

from sqlalchemy_schemadisplay3 import create_uml_graph

import unittest

class cached(object):
    def __init__(self, cacheName): 
        assert isinstance(cacheName, (unicode, str)) 
        self.cacheName = cacheName
        
    def __call__(decorator, f): #@NoSelf
        @wraps(f)
        def wrapper(self, *args, **kwargs):
            try: return getattr(self, decorator.cacheName)
            except AttributeError:
                value = f(self, *args, **kwargs)
                setattr(self, decorator.cacheName, value)
                return value
        return wrapper
    
class cached_property(cached):
    def __call__(decorator, f): #@NoSelf
        return property( cached.__call__(decorator, f) )
      
class Base(object):
    @staticmethod
    def tablename(name):
        "The table name of the class is computed by converting CamelCase to underscore_separator."
        return ''.join( ('', sub("([a-z])([A-Z])", "\\1_\\2", name).lower(), ''))
        
    @staticmethod
    def foreignKeyName(name):
        return Base.tablename(name) + '_id'
    @declared_attr
    def __tablename__(cls): return cls.tablename(cls.__name__) #@NoSelf

    id =  Column(Integer, primary_key=True)
    
    @classmethod
    def get(cls, session, **kwargs):
        try: o = session.query(cls).filter_by(**kwargs).one()
        except NoResultFound:
            o = cls(**kwargs)
            session.add(o)
        return o
    
class WithName(object):
    name = Column(String(64), nullable=False,)

    def __str__(self): return "{self.__class__.__name__} '{self.name}'".format(self=self)

def Reference2(Child, backref, parentReference=None, remote_side=None, nullable=False, ):
    """ Adds a foreign key and a matching many-to-one relation following the naming convention.
    
        @param Child: the name of the child class or the class object itself.
        @param backref: the name of the attribute by which the child class refers to its children (usually in plural).
        @param parentReference: the parent's reference to its child; if given, must be a string in the form 'Parent.child'
        
        The name of the child table is computed by Base.tablename(), i.e. converting CamelCase to underscore_separator.
        The name of the foreign key is the name of the child table with '_id' appended.
    """
    childClassName =  Child.__name__ # Child if isinstance(Child, (str, unicode)) else   
    childTableName = next((cls.__tablename__ for cls in Child.__mro__ if cls.__tablename__))
    if parentReference is None: 
        parentClassName = None
        parentReference = childClassName[0].lower() + childClassName[1:]
    else:
        parentClassName, parentReference = parentReference.split('.') 
    foreignKeyColumnName = Base.tablename(parentReference) + '_id'
     
    @declared_attr
    def foreignKey(cls): return Column(Integer, ForeignKey(childTableName + '.id'), nullable=nullable, ) #@NoSelf
    
    @declared_attr
    def relation(cls): #@NoSelf
        primaryjoin = dict() # do not hardcode primaryjoin=None even if childClassName is None 
        if parentClassName is not None: 
            primaryjoin['primaryjoin'] =("{parentClassName}.{foreignKeyColumnName} == {childClassName}.id"
                                         .format(childClassName=childClassName, 
                                                 foreignKeyColumnName=foreignKeyColumnName, 
                                                 parentClassName=parentClassName,
                                                 ) 
                                         )
        return relationship(Child, backref=backref, remote_side=remote_side, **primaryjoin) # ,  
    
    class Reference2(object): pass
    setattr(Reference2, foreignKeyColumnName, foreignKey)
    setattr(Reference2, parentReference     , relation)
    
    return Reference2 
        
    
Base = declarative_base(cls=Base, metadata=MetaData())
    
class Transition(Base, WithName): 
    __table_args__ = ( UniqueConstraint('name'), )
    transition_action = Table('transition_action', Base.metadata,
         Column('transition_id', Integer, ForeignKey('transition.id')),
         Column('action_id' , Integer, ForeignKey('action.id')),
         UniqueConstraint('transition_id', 'action_id', name='unique_transition_action')
         )            
    actions = relationship('Action', secondary='transition_action',
                                        collection_class = set,
                                        secondaryjoin   = "Transition.id==transition_action.c.transition_id", 
                                        primaryjoin = "Action.id==transition_action.c.action_id",
                                        backref=backref('transitions', collection_class = set))

class Action(Base, WithName): 
    __table_args__ = ( UniqueConstraint('name'), )

    class LockingStrategy(object):
        pessimistic = 'P'
        optimistic  = 'O'
    lockingStrategy = Column( Enum(LockingStrategy.pessimistic, LockingStrategy.optimistic) )   
    
    @cached_property(" linkages")
    def linkages(self):
        linkages = set() # if 2 transfers are linked, the linkage would be added twice
        for arc in self.arcs:
            linkages.update( l for l in arc.alias. referencedLinkages if set(l. referencedAlias.arcs) & set(self.arcs))
            linkages.update( l for l in arc.alias.referencingLinkages if set(l.referencingAlias.arcs) & set(self.arcs))
        return linkages 
    
    @cached_property(" bindingQuery")
    def bindingQuery(self):
        "this query selects the bindings enabling the action"
        q = Query( list(a.alias.alias for a in self.arcs if not a.inhibit) )
        for a in self.arcs:
            if not a.inhibit: q = q.filter(a.criterion)  # filter by places and optionally being in scope
        for l in self.linkages: 
            criterion = l.criterion # filter by being related
            assert  criterion is not None
            foundInhibit = False  
            for arc in ( set(l.referencingAlias.arcs + l.referencedAlias.arcs)) & set(self.arcs):
                if arc.inhibit:
                    assert not foundInhibit 
                    criterion = ~exists().where(criterion & (arc.alias.alias.place_id.in_( set(fro.id for fro in arc.froms)) ))
                    foundInhibit = True
            q = q.filter(criterion) 
        return q
    
    def isEmpty(self): return not self.substitutions
    
    @staticmethod
    def get(session, name, lockingStrategy):  
        try: o = session.query(Action).filter_by(name=name,).one()
        except NoResultFound:
            o = Action(name=name, lockingStrategy=lockingStrategy)
            session.add(o)
        else:
            o.lockingStrategy = lockingStrategy 
        return o

    def fire(self, execution):
        self._print()
        mappings = dict()
        query = self.bindingQuery 
        query = query.params(executionId = execution.id)
        for tokens in ( query.with_session(object_session(self)).all() ):
            if isinstance(tokens, Token): tokens = tokens,
            handle = uuid4().hex
            substitution = Substitution(action=self, execution=execution,
                                        startedAt=datetime.datetime.now(),
                                        handle=handle)
            mapping=dict() # {arcName: tokenValue}
            mappings[handle] = mapping
            for arc, token in zip( (a for a in self.arcs if not a.inhibit), tokens):
                arc.fire(substitution, token)                
                mapping[arc.name] = token.value
        return mappings
    
    def _print(self):
        print self
        for arc in self.arcs: arc._print()
        for l in self.linkages: l._print()
    
class Colour(Base, WithName,): 
#    """ @param knotColour: None if the colour represents knots, otherwise the colour  
#                                    the attribute or relation applies to. Colour instances with
#                                    - empty knotColour will be mapped to knots, 
#                                    - non-empty field names will become descriptors in snapshot classes.
#        @param otherColour: None unless the colour represents a reference, 
#                                    in which case it is the colour of the referenced knots.
#
#    """
#    knot_colour_id = Column(Integer, ForeignKey('colour.id'))
#    knotColour  = relationship("Colour", remote_side='Colour.id', primaryjoin='Colour.id == Colour.knot_colour_id', ) # , backref="attributeColours"
#    
#    other_colour_id = Column(Integer, ForeignKey('colour.id'))
#    otherColour = relationship("Colour", remote_side='Colour.id', primaryjoin='Colour.id == Colour.other_colour_id', ) # , backref="referringColours"
    
    __table_args__ = ( UniqueConstraint('name'), ) # 'knot_colour_id', 
     
    ephemeralPlaceName = Column(String(64), nullable=False,)
    @property
    def ephemeralPlace(self):
        return object_session(self).query(Place).filter_by(colour_id=self.id)\
            .filter_by(name=self.ephemeralPlaceName).one()

    entities = defaultdict(dict) # { engine : { colour.name : entity } }
    @cached_property(" Entity")
    def Entity(colour, ): #@NoSelf
        try: Entity = Colour.entities[object_session(colour).bind][colour.name]
        except KeyError:
            
            def __str__(self): return "{self.colour.knotColour.name}.{self.colour.name} = '{self.knot.value} @ {self.colour.otherColour.name} {self.value}'".format(self=self)
            
            def delete(self):
                for Reference2 in references:
                    for name in Reference2.__dict__.keys():
                        #if isinstance(value, RelationshipProperty):
                        setattr(self, name, None)
                Token.delete(self)
                
            tableName = Base.tablename(colour.name)
            classMembers = dict(
                # when reflecting:__table__ = metadata.tables[tableName],
                _colour_id  = colour.id,
                __str__     = __str__  ,
                delete      = delete   ,
                _entityName = tableName,
                                ) 
            references = list()
            for field in colour.fields.values():
                if field.referencedColour is None:
                    classMembers[field.name] = Column(String())
                else: references.append(
                    Reference2(field.referencedColour.Entity, backref=None,
                               parentReference='{colour.name}.{field.name}'.format(colour=colour, 
                                                                                  field=field),
                               remote_side=field.referencedColour.Entity.id,
                               nullable=True,
                               ) 
                    )
            baseClasses = (WithIdOverride, Token, ) + tuple(references)
            
            Entity = DeclarativeMeta(str(colour.name), baseClasses, classMembers) 
            Entity.__table__.create(bind=object_session(colour).bind, checkfirst=True)
            Colour.entities[object_session(colour).bind][colour.name] = Entity
             
        return Entity

class Field(Base, WithName, 
            Reference2( Colour, 
                        backref('fields', 
                                collection_class = mapped_collection(lambda field: field.name), 
                                ),
                        'Field.colour',
                       ),
            Reference2( Colour, 'referencingFields', 'Field.referencedColour', nullable=True),
            ):
    __table_args__ = ( UniqueConstraint('name', 'colour_id'), )

class Alias(Base, WithName, 
            Reference2(Colour, 'aliases', ), # "Alias.colour" 
            ): 
    __table_args__ = ( UniqueConstraint('name', 'colour_id'), )
     
    @cached_property(" alias")
    def alias(self):
        return aliased(self.colour.Entity, name=self.name) #
    
class Place(Base, WithName, 
            Reference2(Colour, 'places', ), #"Place.colour" 
            ): 
    __table_args__ = ( UniqueConstraint('name', 'colour_id'), ) 

    def isEmpty(self): return not self.tokens
    
    compatible = Table('compatible', Base.metadata,
         Column('referencedPlace_id', Integer, ForeignKey('place.id')),
         Column('referencingPlace_id' , Integer, ForeignKey('place.id')),
         UniqueConstraint('referencedPlace_id', 'referencingPlace_id', name='unique_compatible')
     )            
    compatibleReferencedPlaces = relationship('Place', secondary='compatible',
                                        collection_class = set,
                                        secondaryjoin   = "Place.id==compatible.c.referencedPlace_id", 
                                        primaryjoin = "Place.id==compatible.c.referencingPlace_id",
                                        backref=backref('compatibleReferencingPlaces', collection_class = set))
    
    
class Arc(Base, WithName, # TODO: remove name from here, use alias.name ...
          Reference2(Alias , 'arcs'                           ), 
          Reference2(Action, 'arcs'                           ), 
          Reference2(Place , 'inputs', 'Arc.to', nullable=True), # populated only for Transfers
          Reference2(Colour, 'arcs'  ,                        ),
          ): 
    @declared_attr
    def __tablename__(cls): #@NoSelf
        return 'arc' if cls.__name__ == 'Arc'else None
    
    typ = Column('typ', String(1))
    
    @declared_attr
    def __mapper_args__(cls): #@NoSelf
        if cls.__name__ == 'Arc': return {'polymorphic_on': cls.typ}
        return {'polymorphic_identity': cls._typ}
        
    __table_args__ = ( UniqueConstraint('name', 'action_id'),) 
    
    from_association = Table('from_association', Base.metadata,
         Column('arc_id', Integer, ForeignKey('arc.id')),
         Column('place_id'   , Integer, ForeignKey('place.id'))
     )            
    froms = relationship('Place', secondary     = 'from_association', #@NoSelf
                                  primaryjoin   = "Arc.id==from_association.c.arc_id",
                                  secondaryjoin = "Place.id==from_association.c.place_id", 
                                  backref='outputs')
    negate      = Column(Boolean)
    extendScope = Column(Boolean, ) 
    ignoreScope = Column(Boolean, ) 
    
    inhibit = False
    
    @classmethod
    def get(cls, session, action, alias, **kwargs):
        assert alias is not None
        arcName = alias.name
        try: arc = session.query(cls).filter_by(action=action, name=arcName).one()
        except IntegrityError:
            raise
        except NoResultFound:
            for k, v in cls.defaults.items():
                try: v = kwargs[k]
                except KeyError: kwargs[k] = v
                else: kwargs[k] = "true" == v.lower()
            arc = cls(action=action, name=arcName, alias=alias, colour=alias.colour, **kwargs )
            session.add(arc)
        else:
            for k, v in kwargs.items():
                setattr(arc, k, v)
            #assert not negate or to in froms
        return arc
    
    @cached_property(" criterion")
    def criterion(self):
        c = self.alias.alias.place_id.in_(set(fro.id for fro in self.froms))
        c =  ~c if self.negate else c
        if not self.ignoreScope: c &= self.scopeCriterion
                
        return c & ~exists().where( self.alias.id == Access.token_id)\
                            .where(Access.substitution_id==Substitution.id)\
                            .where(Access.fro_id != self.alias.alias.place_id)\
                            .where(Substitution.completedAt == None)

    @property
    def scopeCriterion(self):
        assert self.ignoreScope is not None
        return exists().where( self.alias.alias.id == Execution.scope_membership.c.token_id)\
                .where( bindparam("executionId", required=True,) == Execution.scope_membership.c.execution_id)
    
    def isEmpty(self): return not self.accesses

    def _print(self):
        print "    ", self, self.alias.colour.name, sorted((x.name) for x in self.froms), '-->', self.to and self.to.name
    

class     Inhibit (Arc, ): 
    _typ='i'   
    defaults = dict(ignoreScope=True, extendScope=None)
    inhibit = True
          
class Test(Arc): # ReadOnly ,  
    _typ='e'
    defaults = dict(ignoreScope=True, extendScope=False)
     
    def fire(self, substitution, token): 
        """ Firing a test is a no-operation, we just record the access
        """
        assert token.place in self.froms
        Access(substitution=substitution, arc=self, token=token)
        
    def undo(self, access): 
        assert access.token.place in self.froms
            
class Transfer(Arc):
    _typ='t'
    defaults = dict(ignoreScope=False, extendScope=True)
     
    def fire(self, substitution, token): 
        assert token.place in (self.froms or [None])
        Access(substitution=substitution, arc=self, token=token)
        token.place = self.to
        
    def undo(self, access):
        assert access.token.place == self.to
        access.token.place = access.fro
                
        
class Linkage(Base, 
              Reference2(Alias   , 'referencingLinkages', 'Linkage.referencedAlias' ),
              Reference2(Alias   , 'referencedLinkages' , 'Linkage.referencingAlias'),
              Reference2(Field   , 'linkages'           , 'Linkage.field'           ),
              ):
    __table_args__ = ( UniqueConstraint('referenced_alias_id', 'referencing_alias_id'), )
    
    def __str__(self): return self.referencedAlias.name + ", " + self.referencingAlias.name
    
    def _print(self): print "      ", self
            
    @cached_property(" criterion")
    def criterion(self):
        return self.resolvedCriterion(None, )
          
    def resolvedCriterion(self, substitution, ):   
        """ Compute a criterion in the context of the substitution.
        
            If the substitution specifies a value for the alias, than that
            will be included in the criterion, otherwise an SQL alias will be used.
        """     
        # filter by being related
        referenced  = self.__resolveAlias(self.referencedAlias , substitution)
        referencing = self.__resolveAlias(self.referencingAlias, substitution)
                          
        c = getattr(referencing, Base.foreignKeyName(self.field.name)) == referenced.id
            
        assert c != False, c # Nonsense: referenced and referencing are unrelated tokens in the substitution
        if c == True:  
            return None # referenced and referencing are related tokens, no criterion can be derived
        
        # at least one of referenced and referencing is an alias: a criterion has been derived  
        return c
            
    @staticmethod  
    def __resolveAlias(alias, substitution):
        """ resolve an alias of the substitution to its value or SQL alias
        """
        if substitution is None: return alias.alias
        else:
            try:
                return substitution.accesses[alias.name].token # token is known
            except KeyError:
                return alias.alias  
        
class Token(Base, 
            Reference2(Place , 'tokens', ), 
            Reference2(Colour, 'tokens', ), 
            ): 
    entityName = Column('entityName', String(16))
    
    @declared_attr
    def __mapper_args__(cls): #@NoSelf
        return {'polymorphic_on': cls.entityName} # colour_id

    def __init__(self, colour, **kwargs):
        Base.__init__(self, colour=colour, place=colour.ephemeralPlace, **kwargs)

    def delete(self):
        for fieldName, field in self.colour.fields.items():
            if field.referencedColour: setattr(self, fieldName, None)
        self.colour = self.place = self.mostRecentExecution = None 
        object_session(self).delete(self)

    # The value of the token can be used to associate a "meaning" to the token.
    # For the coordinator it is a piece of opaque external data it preserves and on demand, echoes back.  
    # Useful (among others) for identifying knots.
    value = Column(String())
    
    most_recent_execution_id = Column(Integer, ForeignKey('execution.id', use_alter=True, name='fk_most_recent_execution'), ) # 
    mostRecentExecution = relationship('Execution', remote_side="Execution.id", 
                                       primaryjoin="Execution.id==Token.most_recent_execution_id",
                                       )   
    
    def getConflictingAccesses(self, execution, arc):
        """ Get the set of accesses that conflict with firing the arc when bound to self 
        """
        accesses = set()
        for access in self.accesses:
            if access.doesConflict(execution, arc, self.place): accesses.add(access)
        return accesses
    
class WithIdOverride(object):
    @declared_attr
    def id(self): return Column(Integer, ForeignKey('token.id'), primary_key=True)
    @declared_attr
    def __mapper_args__(cls): return {'polymorphic_identity': cls._entityName} #@NoSelf _colour_id
        
    
class Execution(Base):
     
    support_association = Table('support_association', Base.metadata,
         Column('supporting_id', Integer, ForeignKey('execution.id')),
         Column('supported_id' , Integer, ForeignKey('execution.id')),
         UniqueConstraint('supporting_id', 'supported_id', name='unique_support_association')
     )            
    supportingExecutions = relationship('Execution', secondary='support_association',
                                        collection_class = set,
                                        secondaryjoin   = "Execution.id==support_association.c.supporting_id", 
                                        primaryjoin = "Execution.id==support_association.c.supported_id",
                                        backref=backref('supportedExecutions', collection_class = set))
    
    scope_membership = Table('scope_membership', Base.metadata,
         Column('execution_id', Integer, ForeignKey('execution.id')),
         Column('token_id'    , Integer, ForeignKey('token.id')),
         UniqueConstraint('execution_id', 'token_id', name='unique_scope_association')
     )
    scope = relationship('Token', secondary='scope_membership',
                                        collection_class = set,
                                        primaryjoin   = "Execution.id==scope_membership.c.execution_id", 
                                        secondaryjoin = "Token.id==scope_membership.c.token_id",
                                        ) # backref=''

    class Status(object):
        running = 'R'
        backout = 'B'
        ending  = 'E'
        zombie  = 'Z' # backout is completed, all dependency info is erased, so it can be revived
        
    status = Column( Enum(Status.running,
                          Status.backout,
                          Status.ending,
                          Status.zombie,
                          ) )
    executionId = Column(String()) 
    
    class TryLater(Exception): pass
    
    def addSupport(self, supportingExecution):
        if supportingExecution.status == Execution.Status.backout:
            raise Execution.TryLater("Conflicted execution {supportingExecution.executionId} is being backed out")
        if self.__doesSupport(supportingExecution):
            supportingExecution.backout()
            raise Execution.TryLater("Conflicted execution {supportingExecution.executionId} must be "
                                     "backed out because it already depends on execution {self.executionId}"
                                     .format(**locals())
                                     )
        self.supportingExecutions.add(supportingExecution)
        
    def __doesSupport(self, execution):
        return self in execution.supportingExecutions or any(self.__doesSupport(x) for x in execution.supportingExecutions)
    
    def backout(self):
        """ Set the status of the execution to 'backout' and cascade to the supported executions.
        """ 
        self.status = Execution.Status.backout
        for x in self.supportedExecutions: x.backout()
    
    @staticmethod
    @contextmanager
    def committing(Session, executionId): #@NoSelf
        with committing(Session) as session:
            try: execution = session.query(Execution).filter_by(executionId=executionId).one()
            except NoResultFound as e:
                e.args = "Execution {executionId} does not exist.".format(**locals()),
                raise
            yield (session, execution)
    
    @staticmethod
    def start(Session, executionId=None):
        """ Start a new execution
        
            @return: a UUID string representing the execution
        """
        if executionId is None: executionId=uuid4().hex
        with committing(Session) as session:
            session.add(Execution(status=Execution.Status.running, executionId=executionId))
        return executionId

    def getPending(self):
        pending = dict()
        for substitution in self.substitutions:
            if substitution.completedAt: continue
            
            try:             action = pending[substitution.action.name]
            except KeyError: action = pending[substitution.action.name] = dict()
            
            mapping = action[substitution.handle] = dict() # {arcName: tokenValue}
            
            for name, access in substitution.accesses.items():
                mapping[name] = access.token.value
        return pending

    def handleDone(self, handles):
        for handle in handles:
            substitution = object_session(self).query(Substitution).filter_by(handle=handle).one()
            assert substitution.execution is self
            substitution.completedAt = datetime.datetime.now()
    
    def handleUndone(self, handles):
        for handle in handles:
            substitution = object_session(self).query(Substitution).filter_by(handle=handle).one()
            assert substitution.execution is self
            substitution.undo()
        if not self.substitutions:
            self.status = Execution.Status.zombie
            # TODO: the support association should be managed on-the-fly, not at the end ...   
            self.supportingExecutions.clear()
            self.supportedExecutions.clear()

    def try2Finish(self):
        if self.status == Execution.Status.ending and not self.supportingExecutions: 
            supportedExecutions = self.supportedExecutions.copy()
            self.delete()
            for execution in supportedExecutions: execution.try2Finish()
        
    def delete(self):
        for substitution in self.substitutions: substitution.delete()
        
        tokens = defaultdict(list)
        for token in self.scope:
            if token.place is None: tokens[token.__table__].append(token)
        for table in Token.metadata.sorted_tables:
            for t in tokens[table]: t.delete()
        self.scope.clear()
        
        self.supportedExecutions.clear()
  
        object_session(self).delete(self)
        
class Substitution(Base,
                        Reference2(Action   , 'substitutions'), 
                        Reference2(Execution, 'substitutions'), 
                        ): 
    handle      = Column(String(32), nullable=False, unique=True)
    startedAt   = Column(DateTime  , nullable=False             )
    completedAt = Column(DateTime)
    
    # mapped_collection(lambda entity: entity.a + entity.b)
    # file:///usr/share/doc/packages/python-SQLAlchemy-doc/doc/orm/collections.html#custom-collections
    accesses = relationship('Access', collection_class=mapped_collection(lambda access: access.arc.alias.name), backref='substitution')
    
    def undo(self):
        for access in self.accesses.values(): access.undo()
        self.delete()
    
    def delete(self):
        for access in self.accesses.values(): access.delete()
        self.action = self.execution = None
        object_session(self).delete(self)
    
    def tokensOf(self, alias):
        try:
            yield self.accesses[alias.name].token
        except KeyError:
            q = object_session(self).query( alias.alias)
            for linkage in alias. referencedLinkages + alias.referencingLinkages: 
                criterion = linkage. resolvedCriterion(self)
                if criterion: q = q.filter(criterion)
            for token in q.all(): 
                yield token
    
    @property   
    def undoable(self,):
        # all(a.token.place is (a.arc.to or a.fro) for a in substitution.accesses.values())
        # a.fro should be omitted! tests do not need to be satisfied in undos
        
        for arc in self.action.arcs:
            if arc.to: # Transfer
                access = self.accesses[arc.alias.name]
                token = access.token
                if token.place is not arc.to: return False
                # check if the tokens of all the aliases linked to arc.alias are compatible
                # with access.fro
                compatibleReferencedPlaceNames  = set( p.name for p in access.fro. compatibleReferencedPlaces)
                for linkage in arc.alias. referencedLinkages:
                    tokens = list(self.tokensOf(linkage.referencedAlias))
                    if not tokens and "NonExistent" not in compatibleReferencedPlaceNames: return False
                    for token in tokens:
                        if token.place not in access.fro.compatibleReferencedPlaces: return False
                compatibleReferencingPlaceNames = set( p.name for p in access.fro.compatibleReferencingPlaces)
                for linkage in arc.alias.referencingLinkages:
                    tokens = list(self.tokensOf(linkage.referencingAlias))
                    if not tokens and "NonExistent" not in compatibleReferencingPlaceNames: return False
                    for token in tokens:
                        if token.place not in access.fro.compatibleReferencingPlaces: return False 
            else: # Test or Inhibit
                pass
        return True
            
class Access(Base, 
             Reference2(Token, 'accesses', 'Access.token', ), 
             Reference2(Arc  , 'accesses',                 ), 
             Reference2(Place, None      , 'Access.fro'  , nullable=True),
             ): 
    __table_args__ = ( UniqueConstraint('token_id', 'arc_id', 'substitution_id'),) # 

    substitution_id = Column(Integer, ForeignKey('substitution.id'), nullable=False, )
    
    def __init__(self, arc, token, substitution):        
        newExecution = substitution.execution
        if arc.extendScope:
            newExecution.scope.add(token)
        else:
            assert arc.extendScope is not None
        if newExecution is not token.mostRecentExecution: 
            # Manage the support relationship of executions                    
            accesses = token.getConflictingAccesses(newExecution, arc)
            for supportingExecution in set( access.substitution.execution for access in accesses):
                newExecution.addSupport(supportingExecution)
    
            token.mostRecentExecution = newExecution
            #assert token.most_recent_execution_id is not None
        Base.__init__(self, arc=arc, token=token, substitution=substitution, 
                            fro=token.place, )
        
    def doesConflict(self, execution, arc, fro):
        """ A and B do not conflict if firing any does not enable the other or firing any enables the other, i.e. iff
                firing A enables B implies firing B enables A and
                firing A disables B implies firing B disables A
        """
        # (self.arc, self.fro) and (arc, fro) conflict if firing (self.arc, self.fro) enabled (arc, fro) 
        # but firing (arc, fro) will not enable (self.arc, self.fro) 
        return  ( (self.arc.to or self.fro) in arc.froms) != ( (arc.to or fro) in self.arc.froms) and self.substitution.execution is not execution 

    def undo(self): self.arc.undo(self)
        
    def delete(self):
        self.token = self.fro = self.substitution = None
        # this assignment has to be the last as it breaks the key computation lambda function
        self.arc = None 
        object_session(self).delete(self)
        
@contextmanager
def committing(Session):
    session = Session() 
    try: yield session
    except:
        session.rollback()
        raise
    else: 
        session.commit()

def createDb(engine):        
    Base.metadata.create_all(engine)    

    
if __name__ == "__main__":
    from sqlalchemy_schemadisplay3 import create_schema_graph
    # create the pydot graph object by autoloading all tables via a bound metadata object
    graph = create_schema_graph(metadata=Base.metadata,
       show_datatypes=False, # The image would get nasty big if we'd show the datatypes
       show_indexes=False, # ditto for indexes
       rankdir='LR', # From left to right (instead of top to bottom)
       concentrate=False # Don't try to join the relation lines together
    )
    graph.write_png('dbschema.png') # write out the file
    # lets find all the mappers in our model
    mappers = []
    for attr in globals().keys():
        #if attr[0] == '_': continue
        try:
            cls = eval(attr)
            mappers.append(class_mapper(cls))
        except UnmappedClassError:
            pass
    
    # pass them to the function and set some formatting options
    graph = create_uml_graph(mappers,
        show_operations=False, # not necessary in this case
        show_multiplicity_one=False # some people like to see the ones, some don't
    )
    graph.write_png('UML.png') # write out the file
    
    class Test1(unittest.TestCase):
        def test(self):
            engine = create_engine('sqlite:///:memory:', echo=True)
            createDb(engine)    
    unittest.main()